/*
 * RectangleGraphical.java
 *
 * Created on 19 maart 2008, 9:45
 *
 * @author wijnand.schepens@hogent.be
 */
package model.graphic.objects;

import controller.BidirectionalHashMap;
import controller.Constants;
import controller.commands.ClearSelectionCommand;
import controller.commands.DeleteCommand;
import controller.commands.EditNodeCommand;
import controller.commands.SetSelectedCommand;
import controller.commands.ShrinkCommand;
import controller.commands.ZoomBoxNodeCommand;
import core.Node;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.FocusEvent;
import model.graphic.objects.addons.RemoveAddon;
import model.graphic.objects.addons.ShrinkAddon;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.LinearGradientPaint;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JViewport;
import model.graphic.locations.FlexibleRelativeLocation;
import model.graphic.locations.Location;
import model.graphic.locations.LowerRightLocation;
import model.graphic.locations.RigidRelativeLocation;
import model.graphic.objects.addons.Addon;
import model.graphic.objects.addons.ZoomAddon;
import load.parent.Store;
import view.GUI.collections.CollectionEditor;
import view.GUI.sidepanels.CustomArgument;
import view.GUI.sidepanels.EditableTextField;
import view.GUI.graphic.GraphicView;
import view.GUI.graphic.SkinInfo;

public abstract class RectangleGraphical extends Graphical {

    protected Location lowerLeft;
    protected Location lowerRight;
    protected Location upperRight;
    private int width;
    private int height;
    protected int minSize;
    private String id;
    protected Line2D l1,  l2;
    protected boolean collapsed = false;
    protected boolean collapseHighlighted = false;
    private RemoveAddon removeAddon;
    private ShrinkAddon shrinkAddon;
    private GResizeHandle resizer = null;
    private boolean boxed = false;
    private GBoxNode boxNode = null;
    private Addon ad = null;
    protected boolean notclickedAddon = true;
    private int previousMouseX;
    private int previousMouseY;

    public RectangleGraphical() {
        this.id = UUID.randomUUID().toString();
    }

    public RectangleGraphical(Location location, int width, int height) {
        this.width = width;
        this.height = height;
        setLocations(location);
        this.id = UUID.randomUUID().toString();
        l1 = new Line2D.Double();
        l2 = new Line2D.Double();

        removeAddon = new RemoveAddon();
        shrinkAddon = new ShrinkAddon();
        ad = this.getShrinkAddon();
    }

    public RectangleGraphical(String name, Location location, int width, int height) {
        this(location, width, height);
        this.setName(name);
    }

    @Override
    public boolean contains(int x, int y) {
        return getRectangle().contains(x, y);
    }

    protected Rectangle getHighlightRectangle() {
        return new Rectangle(getUpperLeft().getX() - getX() - 4, getUpperLeft().getY() - getY() - 4, minSize + 4, minSize + 4);
    }

    public Rectangle getRectangle() {
        return new Rectangle(getUpperLeft().getX() - getX(), getUpperLeft().getY() - getY(), getWidth(), getHeight());
    }

    public void setMinSize(int minSize) {
        this.minSize = minSize;
    }

    public void setResizer(GResizeHandle resizer) {
        this.resizer = resizer;
    }

    public GResizeHandle getResizer() {
        return resizer;
    }

    public void setLocations(Location location) {
        lowerRight = new FlexibleRelativeLocation(location, width, height);
        upperRight = new FlexibleRelativeLocation(location, width, 0);
        lowerLeft = new FlexibleRelativeLocation(location, 0, height);
        setLocation(location);
        if (resizer != null) {
            resizer.setLocations(new LowerRightLocation(lowerRight, new RigidRelativeLocation(location, minSize, minSize)));
        }
    }

    public int getClickLocation(int y) {
        int k = 0;

        if (y > getY() && y < getY() + getHeight() / 3 && !getShrinkAddon().inAddon(0, y)) {
            k = 1;
        } else if (y > getY() + (getHeight() / 3 * 2) && y < getY() + getHeight()) {
            k = 2;
        }
        return k;
    }

    protected static void drawCentered(Graphics g, String text, Rectangle bounds) {
        FontMetrics fm = g.getFontMetrics();
        Rectangle2D textBounds = fm.getStringBounds(text, g);

        int x = bounds.x + bounds.width / 2 - (int) (textBounds.getWidth() / 2);     // center
        int y = bounds.y + bounds.height - (int) textBounds.getHeight() / 2 + fm.getDescent();
        g.drawString(text, x, y);
    }

    protected void drawSkin(SkinInfo info, Graphics2D g){
            //start of drawing with fading colors
            final float[] DIST = {0.2f, 0.8f};
            Color[] colors = {info.getBackColor1(), info.getBackColor2()};
            LinearGradientPaint paint;
            if(info.isReflection()){
                 paint =  new LinearGradientPaint(location.getX(),
                       location.getY(),location.getX(),//+ getWidth(),
                       location.getY() + getHeight() /2,
                       DIST, colors, CycleMethod.REFLECT);
            }else{
                 paint =  new LinearGradientPaint(location.getX(),
                       location.getY(),location.getX(),//+ getWidth(),
                       location.getY() + getHeight()  ,
                       DIST, colors, CycleMethod.NO_CYCLE);
            }
            g.setPaint(paint);
            g.fillRect(location.getX(), location.getY(), getWidth(), getHeight());
            //Background figure
            Image backgroundImage = info.getBackground();
            if(backgroundImage != null){
                g.drawImage(backgroundImage,location.getX() + getWidth() /2
                   - backgroundImage.getWidth(this) /2,location.getY()
                  + getHeight() / 2 - backgroundImage.getHeight(this) /2, null);
            }
    }

    public Point getNameLocation() {
        int x = location.getX();
        int y = location.getY();
        Rectangle bounds = new Rectangle(x, y, getWidth(), getHeight() / 3);
        int xx = bounds.x + bounds.width / 2 - (int) (name.length() / 2);     // center
        int yy = bounds.y + bounds.height - (int) 10 / 2 - 10;
        return new Point(xx, yy);
    }

    @Override
    public int getWidth() {
        return lowerRight.getX() - location.getX();
    }

    @Override
    public int getHeight() {
        return lowerRight.getY() - location.getY();
    }

    public void setWidth(int x) {
        this.width = x;
        setLocations(location);
    }

    public void setHeight(int y) {
        this.height = y;
        setLocations(location);
    }

    public void setWidthHeight(int x, int y) {
        this.width = x;
        this.height = y;
        setLocations(location);
    }

    /**
     * @return the lowerLeft
     */
    public Location getLowerLeft() {
        return lowerLeft;
    }

    /**
     * @return the lowerRight
     */
    public Location getLowerRight() {
        return lowerRight;
    }

    /**
     * @return the upperRight
     */
    public Location getUpperRight() {
        return upperRight;
    }

    /**
     * @return the location
     */
    public Location getUpperLeft() {
        return location;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return the removeAddon
     */
    public RemoveAddon getRemoveAddon() {
        return removeAddon;
    }

    /**
     * @param removeAddon the removeAddon to set
     */
    public void setRemoveAddon(RemoveAddon removeAddon) {
        this.removeAddon = removeAddon;
    }

    /**
     * @return the shrinkAddon
     */
    public ShrinkAddon getShrinkAddon() {
        return shrinkAddon;
    }

    /**
     * @param shrinkAddon the shrinkAddon to set
     */
    public void setShrinkAddon(ShrinkAddon shrinkAddon) {
        this.shrinkAddon = shrinkAddon;
    }

    /**
     * @return the boxed
     */
    public boolean isBoxed() {
        return boxed;
    }

    /**
     * @param boxed the boxed to set
     */
    public void setBoxed(boolean boxed) {
        this.boxed = boxed;
    }

    /**
     * @return the boxNode
     */
    public GBoxNode getBoxNode() {
        return boxNode;
    }

    /**
     * @param boxNode the boxNode to set
     */
    public void setBoxNode(GBoxNode boxNode) {
        this.boxNode = boxNode;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        final GraphicView gv = (GraphicView) this.getParent();
        if (!e.isControlDown() && notclickedAddon) {
            gv.notifyObservers(new ClearSelectionCommand());
            gv.notifyObservers(new SetSelectedCommand(this, true));
            gv.notifyObservers(new CustomArgument("tabRight", 0));
        } else {
            if (!this.isSelected() && notclickedAddon) {
                gv.notifyObservers(new SetSelectedCommand(this, true));
                gv.notifyObservers(new CustomArgument("tabRight", 0));
            }
        }
        if (e.getClickCount() == 1) {
            if (GBoxNode.class.isAssignableFrom(this.getClass())) {
                if (((GBoxNode) this).getZoomAddon().inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
                    gv.notifyObservers(new ZoomBoxNodeCommand(gv,(GBoxNode) this));
                }
            }
            if (this.getRemoveAddon().inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
                gv.notifyObservers(new DeleteCommand(this));
                gv.notifyObservers(new CustomArgument("tabRight", 1));
            }
            if (this.getShrinkAddon().inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
                gv.notifyObservers(new ShrinkCommand(this));
            }
        } else if (e.getClickCount() == 2 && notclickedAddon) {
            final JLayeredPane jp = getRootPane().getLayeredPane();
            final JPanel p = new JPanel();
            p.setLayout(new BorderLayout());
            p.setSize(this.getWidth() - 6, 16);
            Component tab = ((JTabbedPane)this.getParent().getParent().getParent().getParent()).getSelectedComponent();
            Point delta = ((JViewport)this.getParent().getParent()).getViewPosition();
            if (this.getClickLocation(e.getY() + this.getY()) == 1) {
                p.setLocation((int) (gv.getRootPane().getLocation().getX() + tab.getLocation().getX() + this.getNameLocation().getX() - (this.getWidth() - 6) / 2 - delta.getX()), (int) (gv.getRootPane().getLocation().getY() + tab.getLocation().getY() + this.getNameLocation().getY() + p.getHeight() + 10 - delta.getY()));
                final EditableTextField et = new EditableTextField(this.getName());
                final RectangleGraphical n = this;
                et.setHorizontalAlignment(JTextField.CENTER);
                et.addActionListener(new ActionListener() {

                    public void actionPerformed(ActionEvent e) {
                        gv.notifyObservers(new EditNodeCommand(Constants.getNameString(), n, ((JTextField) e.getSource()).getText()));
                        ((JTextField) e.getSource()).setVisible(false);
                        et.setVisible(false);
                        jp.remove(p);
                        gv.notifyObservers(new SetSelectedCommand(n, true));
                        gv.notifyObservers(new CustomArgument("tabRight", 0));
                        gv.repaint();
                    }
                });
                et.addFocusListener(new FocusListener() {

                            public void focusGained(FocusEvent e) {
                                //
                            }

                            public void focusLost(FocusEvent e) {
                                jp.remove(p);
                            }
                        });
                p.add(et, BorderLayout.CENTER);
                jp.setLayer(p, 10);
                jp.add(p);
                et.requestFocus();
            } else if (this.getClickLocation(e.getY() + this.getY()) == 2) {
                if (GNode.class.isAssignableFrom(this.getClass())) {
                    final GNode gn = (GNode) this;
                    BidirectionalHashMap sync = BidirectionalHashMap.getInstance();
                    Node no = (Node) sync.get(gn);
                    if (gn.getValue() != null) {
                        if(CollectionEditor.isCollection(((Store)no).getValue())){
                            CollectionEditor ce = new CollectionEditor(((Store)no).getValue(),gn);
                            ce.setVisible(true);
                        }else{
                            p.setLocation((int) (this.getRootPane().getLocation().getX() + tab.getLocation().getX() + gn.getValueLocation().getX() - (gn.getWidth() - 6) / 2 - delta.getX()),
                                          (int) (this.getRootPane().getLocation().getY() + tab.getLocation().getY() + gn.getValueLocation().getY() + p.getHeight() + 10 - delta.getY()));
                            final EditableTextField et = new EditableTextField(gn.getValue());
                            et.setHorizontalAlignment(JTextField.CENTER);
                            et.addActionListener(new ActionListener() {

                                public void actionPerformed(ActionEvent e) {
                                    gv.notifyObservers(new EditNodeCommand(Constants.getValueString(), gn, ((JTextField) e.getSource()).getText()));
                                    ((JTextField) e.getSource()).setVisible(false);
                                    et.setVisible(false);
                                    jp.remove(p);
                                    gv.notifyObservers(new SetSelectedCommand(gn, true));
                                    gv.notifyObservers(new CustomArgument("tabRight", 0));
                                    gv.repaint();
                                }
                            });
                            et.addFocusListener(new FocusListener() {

                                public void focusGained(FocusEvent e) {
                                    //
                                }

                                public void focusLost(FocusEvent e) {
                                    jp.remove(p);
                                }
                            });
                            p.add(et, BorderLayout.CENTER);
                            jp.setLayer(p, 10);
                            jp.add(p);
                            et.requestFocus();
                        }
                    }
                }
            }
        }

        gv.repaint();

    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.dragStartPoint = e.getPoint();
        this.dragEndPoint = e.getPoint();
        GraphicView gv = (GraphicView) this.getParent();
        if (!e.isControlDown() && notclickedAddon) {
            gv.notifyObservers(new ClearSelectionCommand());
            gv.notifyObservers(new SetSelectedCommand(this, true));
            gv.notifyObservers(new CustomArgument("tabRight", 0));
        } else {
            if (!this.isSelected() && notclickedAddon) {
                gv.notifyObservers(new SetSelectedCommand(this, true));
                gv.notifyObservers(new CustomArgument("tabRight", 0));
            }
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        GraphicView gv = (GraphicView) this.getParent();
        if (GBoxNode.class.isAssignableFrom(this.getClass())) {
            ad = ((GBoxNode) this).getZoomAddon();
            if (ad.inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
                ad.setAddonHighlighted(true);
                notclickedAddon = false;
            } else {
                ad.setAddonHighlighted(false);
                notclickedAddon = true;
            }
        }
        if (this.getRemoveAddon().inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
            ad = this.getRemoveAddon();
            ad.setAddonHighlighted(true);
            notclickedAddon = false;
        } else if (this.getShrinkAddon().inAddon(e.getX() + this.getGraphLocation().getX(), e.getY() + this.getGraphLocation().getY())) {
            ad = this.getShrinkAddon();
            ad.setAddonHighlighted(true);
            notclickedAddon = false;
        } else if (!ZoomAddon.class.isAssignableFrom(ad.getClass())) {
            ad.setAddonHighlighted(false);
            notclickedAddon = true;
        }
        gv.repaint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        GraphicView gv = (GraphicView) this.getParent();
        for (Graphical g : gv.getAllNodes()) {
            RectangleGraphical rg = (RectangleGraphical) g;
            if (rg.isSelected()) {
                //linksklik release
                int jumpx = 0;
                int jumpy = 0;
                int xn = rg.getUpperLeft().getX();
                int yn = rg.getUpperLeft().getY();
                if (gv.isGridOn() && gv.isSnapped()) {
                    jumpx = xn % 25;
                    jumpy = yn % 25;
                    xn = xn - jumpx;
                    yn = yn - jumpy;
                    if (!rg.isBoxed() && !GTransmitterNode.class.isAssignableFrom(rg.getClass())) {
                        rg.getUpperLeft().setXY(xn, yn);
                        rg.setLocations(rg.getUpperLeft());
                    }
                }
                if (GBoxNode.class.isAssignableFrom(rg.getClass()) && !rg.isBoxed()) {
                    GBoxNode bn = (GBoxNode) rg;
                    for (Graphical gr : bn.getInternalObjects()) {
                        if (RectangleGraphical.class.isAssignableFrom(gr.getClass())) {
                            RectangleGraphical intern = (RectangleGraphical) gr;
                            intern.getUpperLeft().setXY(intern.getUpperLeft().getX() - jumpx, intern.getUpperLeft().getY() - jumpy);
                            intern.setLocations(intern.getUpperLeft());
                        }
                    }
                }
            }
        }
        //rechtsklikrelease
        if (e.isPopupTrigger()) {
            gv.notifyObservers(new ClearSelectionCommand());
            gv.notifyObservers(new SetSelectedCommand(this, true));
            if (this.isBoxed()) {
                gv.getPopup().menuBoxedInNode();
            } else if (GBoxNode.class.isAssignableFrom(this.getClass())) {
                gv.getPopup().menuBoxNode();
            } else {
                if (this.getType().startsWith("StoreIO")) {
                    gv.getPopup().menuStore();
                } else {
                    gv.getPopup().menuNode();
                }

            }
            gv.getPopup().getPopup().show(this, e.getX(), e.getY());
        }
        gv.notifyObservers(new SetSelectedCommand(this, true));
        gv.repaint();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (this.notclickedAddon) {
            GraphicView gv = (GraphicView) this.getParent();
            if (this.isBoxed()) {
                this.setWidth(this.getWidth());
                int x = e.getX();
                int y = e.getY();
                int xn = this.getX() + x - dragEndPoint.x;
                int yn = this.getY() + y - dragEndPoint.y;

                GBoxNode bn = this.getBoxNode();
                if (xn > 0 && xn < gv.getWidth() - this.getWidth() && yn > 0 && yn < gv.getHeight() - this.getHeight()) {
                    this.getUpperLeft().setXY(xn, yn);
                    this.setLocations(this.getUpperLeft());
                    previousMouseX = (int) e.getLocationOnScreen().getX();
                    previousMouseY = (int) e.getLocationOnScreen().getY();
                } else {
                    Robot r;
                    try {
                        r = new Robot();
                        r.mouseMove(previousMouseX, previousMouseY);
                    } catch (AWTException ex) {
                        Logger.getLogger(GNode.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            } else {
                for (Graphical g : gv.getAllNodes()) {
                    RectangleGraphical rg = (RectangleGraphical) g;
                    if (rg.isSelected()) {
                        rg.setWidth(rg.getWidth());
                        int x = e.getX();
                        int y = e.getY();
                        int xn = rg.getX() + x - dragEndPoint.x;
                        int yn = rg.getY() + y - dragEndPoint.y;
                        int xo = x - dragEndPoint.x;
                        int yo = y - dragEndPoint.y;
                        if (xn > 0 && xn < gv.getWidth() - rg.getWidth() && yn > 0 && yn < gv.getHeight() - rg.getHeight()) {
                            rg.getUpperLeft().setXY(xn, yn);
                            rg.setLocation(rg.getUpperLeft());
                            //move the inner nodes in the boxnode
                            if (GBoxNode.class.isAssignableFrom(rg.getClass())) {
                                GBoxNode bn = (GBoxNode) rg;
                                for (Graphical gr : bn.getInternalObjects()) {
                                    if (RectangleGraphical.class.isAssignableFrom(gr.getClass())) {
                                        RectangleGraphical gen = (RectangleGraphical) gr;
                                        gen.getUpperLeft().setXY(gen.getX() + xo, gen.getY() + yo);
                                        gen.setLocation(gen.getUpperLeft());
                                    }
                                }
                            }
                            previousMouseX = (int) e.getLocationOnScreen().getX();
                            previousMouseY = (int) e.getLocationOnScreen().getY();
                        } else {
                            Robot r;
                            try {
                                r = new Robot();
                                previousMouseX = (int) e.getLocationOnScreen().getX();
                                previousMouseY = (int) e.getLocationOnScreen().getY();
                                r.mouseMove(previousMouseX, previousMouseY);
                            } catch (AWTException ex) {
                                Logger.getLogger(GNode.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
                }
            }
            gv.repaint();
        }
    }
}